package com.zwh.se.io;

import java.io.*;

/**
 * File:是一个封装着文件/目录信息的类，我们可以对其本身进行各类操作，
 * IO流:I/O，Input/Output的缩写，其抽象就是用于处理设备之间的数据的传输。可以将IO流理解为是一个管子，它是程序与数据源之间沟通的桥梁。
 * TIPS:IO是数据传输的抽象,所以数据源是多种多样的,无论是文件内容、内存、数据库资源、网络资源等均是属于数据源。
 * <p>
 * 基于这点，java设计出有四个抽象基类(所有IO衍生类的父抽象类)。
 * InputStream：字节输入流
 * OutputStream：字节输出流
 * Reader：字符输入流
 * Writer：字符输出流
 * 输入流简单来讲就是读，输出流简单来讲就是写。
 * 而所谓字节流和字符流指的是在读或者写的时候，对对应操作事物的兼容与优化（比如你想操作字符最好使用字符流，如果操作的不是字符就千万不要使用字符流）
 *
 * 以下是使用文件流进行的演示Demo
 */
public class FileInputStreamOutputStreamDemo {
    public static void main(String[] args) throws IOException {
        System.out.println("----------演示文件字节输出流的效果----------");
        //准备数据(源)
        byte[] bytes = {'a', 'b', 'c'};
        String str = "()这是一次字节输出流测试";//此时数据存于内存当中
        //准备file对象(目标)
        File file = new File("file/outputStream.txt");
        //io操作(把内存中的数据存入文件中)
        OutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(file);
            //将指定字节写入输出流。 write的一般约定是将一个字节写入输出流，鉴于此特点，如果提供的数据超过了8bit，那将会只保留低8位。
            outputStream.write(65);
            outputStream.write(Integer.MAX_VALUE);
            outputStream.write(67);
            outputStream.write(bytes);
            // write(byte b[], int off, int len)。
            // byte b[]：操作字节  int off：偏移值，也就是跳过几个字节开始写  int len：写入的字节数是多少个
            outputStream.write(str.getBytes(), 2, str.getBytes().length - 2);
            // 清空缓冲区并强制写出任何缓冲的输出字节。简单来说就是清除缓存。为什么要做这步形象地理解就是管道传输中往往有可能会存在残留的情况,
            // 如果在残留还在的情况下就去掉管道(close)的话,管道里就很有可能有些数据都还没有写入或者说没清干净,所以需要先调用这个方法再close
            outputStream.flush();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        System.out.println("----------演示文件字节输入流的效果----------");
        //根据文件路径创建File对象（读的时候记得需要做的事情,本demo因为放一块了所以可以不写罢了）
        file = new File("file/output.txt");
        if (!file.exists()) {
            file.createNewFile();
        }
        try {
            FileInputStream fileInputStream = new FileInputStream(file);
            //int read() 从该输入流读取一个字节的数据，一个字节一个字节地读。如果还没有输入可用，则此方法会阻塞。
            //为什么read()的返回值是int类型的,而不是byte类型?这是因为read方法底层做了处理，让返回的数据都是"正数"。为的是避免如果字节返回的值正好是-1的话,能够判断到底是读入字节,还是到文件结尾了。
            // 返回值：数据的下一个字节，如果到达文件末尾，则为-1 。
            int result;
            while ((result = fileInputStream.read()) != -1) {
                System.out.println(result);
            }
            //值得注意的点是，这个输入流属于一整个字节流对象的操作行为，所以上面读完后下面想继续使用该fileInputStream的话就没得读了，所以只能新建再读
            //TIPS:可以尝试把new对象注释后再运行查看效果
            fileInputStream = new FileInputStream(file);
            //提高效率，搞一个缓冲区(最主要是可以演示完整读取一个字符的效果)
            //缓冲。指每一次IO中可以读取3个字节，一般而言，越大越好，因为一次IO会耗费不少的资源，但缓冲是冲到内存，所以要考虑内存溢出的问题
            //为啥是3呢，是因为 UTF-8 编码中，一个英文字为一个字节，一个中文为三个字节，为的是可以显示出中文出来
            //否则可以尝试改成2或者4输出一下看看
            byte[] buffer = new byte[3];
            int len;
            // read(byte b[]) 读取缓冲数量的字节，实际能读到多少个就返回多少个，-1代表已经到底了，读不到了
            while ((len = fileInputStream.read(buffer)) != -1) {
                System.out.println(new String(buffer, 0, len));
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}